/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Test application for FastProtocol
 * \author Antti Maula <antti.maula@tkk.fi>
 */

#define _GNU_SOURCE
#include <string.h>
#include "protocolDispatcher.h"
#include "protocol/fastProtocolHdr.h"
#include "protocol/hubctrlHdr.h"
#include "owndebug.h"
#include "ownutils.h"
#include <signal.h>
#include <curses.h>
#include <assert.h>

//*****************************************************************************
//*****************************************************************************
#define GIMNETAP_FASTPROTOCOL_TESTAPP_VERSIONSTRING "v0.0-Alpha"
//*****************************************************************************
//*****************************************************************************

static volatile int run = 1;
static int verbose = ODINFO;
static ownThreadHandle signalHandlerThread = NULL;
static ownThreadHandle protocolDispProcessorThreadHandle = NULL;
static ownMutexHandle lock;
static ownCondHandle cond;
static WINDOW *w = NULL;
//*****************************************************************************
static uint32_t targetNodeId = 0x00000000;
static uint32_t localNodeId = 0x00000000;
static uint32_t messageSeq = 0;
static uint32_t lastReceivedMessageSeq_async = 0;
static TProtocolDisp aplink;
static char test_payload[4096] = "";
static size_t test_payload_size = 0;
static unsigned int received_in_seq = 0;
static uint64_t received_data_in_seq = 0;

//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

static void Lock()
{
  ownMutex_Lock(lock);
}
//*****************************************************************************

static void Unlock()
{
  ownMutex_Unlock(lock);
}
//*****************************************************************************

static void notifyReply(const int id)
{
  Lock();
  lastReceivedMessageSeq_async = id;
  ownCond_Broadcast(cond);
  Unlock();
}
//*****************************************************************************

static int waitReply(const int id, const int timeout)
{
  int result = 0;
  ownTime_ms_t begin = ownTime_get_ms();
  ownTime_ms_delta_t left = timeout;

  Lock();
  while(lastReceivedMessageSeq_async < id && left > 0) {
    ownCond_Wait(cond, lock, left);
    left = ownTime_get_ms_left(timeout, begin);
  }
  
  result = (lastReceivedMessageSeq_async >= id);
  Unlock();
  
  return result;
}
//*****************************************************************************

static int protocolDispProcessorThread(void *p)
{
  TProtocolDisp *link = (TProtocolDisp *)p;
  while(run) {
    if (protocolDispProcess(link, 1000)) {
      dPrint(ODTEST,"Process returned error!");
      run = 0;
    }
  }
  return 0;
}
//*****************************************************************************

static void processFastProtocolFrame(const TFastProtocolHeader *framehdr,
                                     const unsigned char *payload,
                                     const unsigned int payloadSize,
                                     void *userPtr)
{  
  if (received_in_seq < 10) {
    printw("Received FastProtocolFrame from 0x%08x, contains %u bytes of payload\n",
           framehdr->source_id, payloadSize);

  } else {
    printw("\rReceived %lu FastProtocolFrames in sequence, total of %llu bytes",
           received_in_seq, received_data_in_seq);
    
  }
  received_data_in_seq += payloadSize;
  ++received_in_seq;
}
//*****************************************************************************

static void hubctrlProtocolCallback(int protocol_number,
                                    const unsigned char *data,
                                    int size,
                                    void *userPtr)
{ 
  // This function gets called when a HubCtrlProtocol packet is received
  // from Hub.
  const THubCtrlProtocolHeader *frame = (const THubCtrlProtocolHeader *)data;
  
  if (size >= sizeof(THubCtrlProtocolHeader) &&
      size == frame->protocol_id.total_size) {
    // Protocol size match; We have enough data to process call
    if (frame->request == KHubCtrlRequestQueryLocalNodeId &&
        frame->flags & KHubCtrlFlagReply &&
        size == sizeof(THubCtrlProtocolHeader)+sizeof(THubCtrlSubHeaderNodeInfo)) {
      THubCtrlSubHeaderNodeInfo *ni = (THubCtrlSubHeaderNodeInfo *)(frame+1);
      
      printw("\rOwn address for this module is '0x%08x' (Link delay %u us)\n",
             ni->nodeid, ni->link_delay_us);
      localNodeId = ni->nodeid;
    }
    
  } else {
    dPrint(ODWARN,"Warning: Received invalid HubCtrlProtocol frame!");
    
  }
}
//*****************************************************************************

static void fastProtocolCallback(int protocol_number,
                                 const unsigned char *data,
                                 int size,
                                 void *userPtr)
{
  // This function gets called when a FastProtocol packet is received
  // from Hub.
  const TFastProtocolHeader *frame = (const TFastProtocolHeader *)data;

  if (size >= sizeof(TFastProtocolHeader) &&
      size == frame->protocol_id.total_size) {
    // Protocol size match; We have enough data to process call
    processFastProtocolFrame(frame,
                             data+sizeof(TFastProtocolHeader),
                             size-sizeof(TFastProtocolHeader),
                             userPtr);

  } else {
    dPrint(ODWARN,"Warning: Received invalid FastProtocol frame!");
    
  }
}
//*****************************************************************************


static void requestOwnId(TProtocolDisp *link)
{
  THubCtrlProtocolHeader frame;

  // Clear header.
  memset(&frame, 0x00, sizeof(frame));
  
  // Assign identification information
  protocolIdentificationConstructHeader(&frame.protocol_id,
                                        HUBCTRLPROTOCOL_NUMBER,
                                        sizeof(THubCtrlProtocolHeader));
  
  // Copy parameters
  frame.request = KHubCtrlRequestQueryLocalNodeId;
  frame.flags = 0x00;
  frame.subheader_count = 0;
  frame.result = 0;
  frame.message_id = ++messageSeq;
  
  protocolDispSendPacket(link,(void*)&frame,sizeof(frame));
}
//*****************************************************************************

static void showwait()
{
  const char value[] = "|/-\\";
  static int phase = 0;
  printw("\r %c: ", value[phase]);
  phase = (phase + 1) % 4;
}
//*****************************************************************************

static void test_SimpleSend(void)
{ 
  static unsigned int repeat = 1;
  static unsigned int interval_ms = 100;
  if (!targetNodeId) targetNodeId = localNodeId;

  printw("Testing Simple send:\n");
  printw("TargetNodeId [0x%08x]: ",targetNodeId);
  scanw("%x", &targetNodeId);
  
  char newpayload[sizeof(test_payload)];
  printw("Payload? [Have %u bytes]: ", test_payload_size);
  getnstr(newpayload, sizeof(newpayload) );
  const unsigned int newpayloadsize = strlen(newpayload);  
  if (newpayloadsize > 0) {
    test_payload_size = newpayloadsize+1;
    memcpy(test_payload, newpayload, test_payload_size);
  }
  
  printw("Repeat? [%u times]: ", repeat);
  scanw("%u", &repeat);

  if (repeat > 1) {
    printw("Interval? [%u ms]: ", interval_ms);
    scanw("%u ", &interval_ms);
  }



  halfdelay(1);
  
  const double begin = ownTime_get_s();
  // Construct Query
  const unsigned int testbuffer_size = ( sizeof(TFastProtocolHeader) + 
                                         test_payload_size );
  uint8_t * testbuffer = (uint8_t *) malloc(testbuffer_size);
  assert(testbuffer != NULL);


  TFastProtocolHeader *fpframe = (TFastProtocolHeader*)testbuffer;
  uint8_t *payload = (void*)(fpframe+1);

  // Setup FastProtocolFrame
  fpframe->source_id = 0x00;
  fpframe->target_id = targetNodeId;
  fpframe->flags = 0x00;
  fpframe->ttl = FASTPROTOCOL_DEFAULT_TTL;
  fpframe->statusvalue = 0x00;
  fpframe->protocol_id.protocol_number = FASTPROTOCOL_NUMBER;
  fpframe->protocol_id.total_size = testbuffer_size;

  // copy payload
  memcpy(payload, test_payload, test_payload_size);

  // Send!
  printw("Sending %u times a FastProtocol frame total of %u bytes long\n", 
         repeat, testbuffer_size);
  int repeat_count;
  for(repeat_count=0; repeat_count < repeat; ++repeat_count) {
    if (repeat_count > 0) ownSleep_ms(interval_ms);
    protocolDispSendPacket(&aplink,(void*)testbuffer,testbuffer_size);
  }

  // Output management.
  if (received_in_seq) {
    received_in_seq = 0;
    received_data_in_seq = 0;
    printw("\n");
  }
  
  printw("%s(): Operation completed in %.2f ms\n", 
         __FUNCTION__, 1000.0*(ownTime_get_s()-begin));  
}
//*****************************************************************************

static int commandHandler(int value)
{
  int r = 1;
  printw("\n");
  switch(value) {

  case 's':
    test_SimpleSend();
    break;

  case 'q':
    printw("Terminate program...\n");
    r = 0;
    break;

  case 'h':
    printw("\nAvailable Tests:\n");
    printw(" 's'       Simple sending test\n");
    printw(" 'q'       Quit\n");
    printw("\n");
    break;
  }
  return r;
}
//*****************************************************************************

static int ExecuteService(TProtocolDisp *link)
{
  int result = 0;

  // Init sync mechanisms
  lock = ownMutex_Init();
  cond = ownCond_Init();
  

  // Register callback,
  result = protocolDispRegisterCallback(link, FASTPROTOCOL_NUMBER, 
                                        fastProtocolCallback, NULL);
  result |= protocolDispRegisterCallback(link, HUBCTRLPROTOCOL_NUMBER, 
                                         hubctrlProtocolCallback, NULL);
  assert(result == 0);

  /*
  // Setup the default FastProtocol carrier frame
  memset(g_replyfpframe, 0x00, sizeof(TFastProtocolHeader));
  g_replyfpframe->source_id = 0x00;
  g_replyfpframe->target_id = targetNodeId;
  g_replyfpframe->flags = 0x00;
  g_replyfpframe->ttl = FASTPROTOCOL_DEFAULT_TTL;
  g_replyfpframe->statusvalue = 0x00;
  g_replyfpframe->protocol_id.protocol_number = FASTPROTOCOL_NUMBER;
  g_replyfpframe->protocol_id.total_size = sizeof(g_replybuffer);
  */

  // Send reques for own id
  requestOwnId(link);

  // Init curses
  w = initscr(); 
  cbreak(); 
  //  noecho();
  nonl();
  intrflush(stdscr, FALSE);
  keypad(stdscr, TRUE);
  //nodelay(w, 1);
  halfdelay(1);
  atexit( (void (*)(void))endwin );

  int value;

  // Start processor
  protocolDispProcessorThreadHandle = ownThread_Create((void*)protocolDispProcessorThread, link);

  while (run) {
    showwait();
    
    // Read input
    value = getch();
    
    // Handle input
    if (value != ERR) {
      clear();
      refresh();

      run = commandHandler(value);
    }
  }
  return result;
}
//*****************************************************************************

static void showUsage()
{
}
//*****************************************************************************

static int SignalHandlerThread(void *t)
{
  dPrint(8, "Signal handler thread (%ld) starting...", pthread_self());

  while (run) {
    // Now, wait for requested signals to arrive
    int sig;
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGPIPE);
    sigaddset(&mask, SIGHUP);
    sigwait(&mask, &sig);

    // Got signal, sigwait returned!
    dPrint(ODSTATUS,"Got Signal '%d' (%s)", sig, strsignal(sig));

    // What to do?
    switch(sig) {
    case SIGINT:
    case SIGTERM:
    case SIGPIPE:
    case SIGHUP:
      run = 0;
      ownThread_Signal(signalHandlerThread, SIGINT);
      ownSleep_ms(1000);
      break;
      
    default:
      dPrint(ODSTATUS,"Unhandled signal! Ignore!");
      break;
    }
  }
  return 0;
}
//*****************************************************************************

static void InitSignalHandler(void)
{
  // Now; Set to BLOCK ALL SIGNALS
  sigset_t mask;
  sigemptyset(&mask);
  sigaddset(&mask, SIGINT);
  sigaddset(&mask, SIGTERM);
  sigaddset(&mask, SIGPIPE);
  sigaddset(&mask, SIGHUP);
  pthread_sigmask(SIG_BLOCK, &mask, NULL);
  
  // Start signal handler thread
  signalHandlerThread = ownThread_Create((void*)SignalHandlerThread, NULL);
}
//*****************************************************************************

int main(int argc, char *argv[]) 
{
  int paramParseLoop = 1;
  //  unsigned int service_address = 0x00000001;
  //  const char *authenticationFile = NULL;
  char targetAddress[255];
  int targetTcpPort = 0; // Port to use
  int r = 0;

  
  // Set up utils (debugs mostly)
  debugInit();
  debugSetGlobalDebugLvl(ODINFO);
  debugSetLogFilename("fastprotocol_test.log");
  debugRemoveMode( DEBUG_MODE_TIMESTAMP_HH_MM_SS | DEBUG_MODE_TIMESTAMP_HH_MM_SS_MS );
  debugAddMode( DEBUG_MODE_TIMESTAMP_DELTA_SS_US );

  // Now; Set to BLOCK ALL SIGNALS
  InitSignalHandler();
  
  // Print version
  printf("\nGIMnet FastProtocol TestApplication - %s\n\n", GIMNETAP_FASTPROTOCOL_TESTAPP_VERSIONSTRING);
  
  
  // Decode command line parameters.
  while(paramParseLoop) {
    int c;
    c = getopt(argc, argv, "u:p:qvh");
    switch(c) {
      
    case 'p':
      // Port to use
      targetTcpPort = atoi(optarg);
      break;
      
    case 'u':
      // Host to
      strncpy(targetAddress, optarg, sizeof(targetAddress)-1);
      targetAddress[sizeof(targetAddress)-1] = '\0';
      break;
      
      //////////////////////////////////
      //// GENERIC:                 ////
      //////////////////////////////////

    case 'v':
      // Increase only if not previously set to 0 (by 'quiet')
      if (verbose > 0) verbose++;
      debugSetGlobalDebugLvl(verbose);
      break;
      
    case 'q':
      verbose = 0;
      debugSetGlobalDebugLvl(verbose);
      debugSetLogFilename(NULL);
      break;
      
    case 'h':
    case '?':
      showUsage();
    exit(1);
    break;
    
    case -1:
      paramParseLoop = 0;
      break;
    }
  }

  // Now; got all parameters -> start processing
  // 1. Initialize TCP link
  int v = 0;
  v = protocolDispInit(&aplink);
  dPrint(ODTEST,"Connecting to '%s' at port %u", targetAddress, targetTcpPort);
  v &= protocolDispConnect(&aplink, targetAddress, targetTcpPort);
  if (v == PROTOCOLDISPATCHER_OK) {
    // OK!
    r = ExecuteService(&aplink);

  } else {
    // FAILED!
  } 

  printf("tcpHub exiting.\r\n");

  return r;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
